﻿Imports Trinkets.Core
Imports System.Reflection

Public Class EditWindow
    Inherits BaseWindowHandler

    Private _existingEntity As BaseEntity = Nothing

    Private Sub New(ByVal table As TableSchema, ByVal database As CsvDatabase, ByVal title As String)
        MyBase.New(table, database, title, 500, 600)
    End Sub

    Friend Shared Function Generate(ByVal database As CsvDatabase, _
                                    ByVal table As TableSchema, _
                                    Optional ByVal existingEntity As BaseEntity = Nothing,
                                    Optional ByVal parentEntity As BaseEntity = Nothing) As EditWindow

        ' Determine what the title should be for the new window
        Dim Title As String = "Insert " + table.SingularCaption + " Record"
        If existingEntity IsNot Nothing Then Title = "Edit " + table.SingularCaption + " Record"

        ' Create a new window
        Dim Result As New EditWindow(table, database, Title)
        Result._existingEntity = existingEntity

        ' Add fields
        Dim Location As New Point(12, 12)
        For Each Field As FieldSchema In table.AllFields
            If Not Field.IsPrimaryKey Then

                ' Add the label
                Location.X = 12
                Dim Label As New Label
                Label.Content = Field.DisplayName + ":"
                Label.HorizontalAlignment = HorizontalAlignment.Left
                Label.VerticalAlignment = VerticalAlignment.Top
                Label.Margin = New Thickness(Location.X, Location.Y, 0, 0)
                Result._grid.Children.Add(Label)
                Location.X += 128

                ' Add the data entry control
                Dim Control As Control = Result.CreateControl(Location, Field)
                Result._grid.Children.Add(Control)

            End If
        Next

        ' Add child tables
        Location.X = 12
        For Each ChildTable As ChildTableSchema In table.ChildTables

            ' Add the child table label
            Dim ChildLabel As New Label
            ChildLabel.Content = ChildTable.Description
            ChildLabel.HorizontalAlignment = HorizontalAlignment.Left
            ChildLabel.VerticalAlignment = VerticalAlignment.Top
            ChildLabel.Margin = New Thickness(Location.X, Location.Y, 0, 0)
            ChildLabel.FontWeight = FontWeights.Bold
            Result._grid.Children.Add(ChildLabel)
            Location.Y += 30

            ' Add the child table data grid
            Dim ChildTableSchema As TableSchema = table.Database.Tables(ChildTable.TableName)
            Dim ChildDataGrid As New DataGrid
            If existingEntity Is Nothing Then
                DataGridManager.Update(ChildDataGrid, database, ChildTableSchema, False)
            Else
                DataGridManager.Update(ChildDataGrid, database, ChildTableSchema, table.PrimaryKey, table.GetPrimaryKeyValue(existingEntity))
            End If
            ChildDataGrid.HorizontalAlignment = HorizontalAlignment.Stretch
            ChildDataGrid.VerticalAlignment = VerticalAlignment.Top
            ChildDataGrid.Margin = New Thickness(Location.X, Location.Y, 12, 0)
            ChildDataGrid.Height = 120
            ChildDataGrid.Tag = ChildTableSchema
            Result._grid.Children.Add(ChildDataGrid)
            Location.Y += ChildDataGrid.Height + 6

            ' Create the button to insert a new record
            Dim RightX As Integer = 12
            If ChildTable.CanAddRecords Then
                Dim InsertButton As Button = ControlGenerator.GenerateButton("Add", 55, 23, New Thickness(0, Location.Y, RightX, 0))
                InsertButton.HorizontalAlignment = HorizontalAlignment.Right
                InsertButton.Tag = ChildDataGrid
                AddHandler InsertButton.Click, AddressOf Result.HandleChildTableInsertButtonClick
                Result._grid.Children.Add(InsertButton)
                RightX += 61
            End If

            ' Create the button to open an existing record
            If ChildTable.CanViewRecords Then
                Dim OpenButton As Button = ControlGenerator.GenerateButton("Open", 55, 23, New Thickness(0, Location.Y, RightX, 0))
                OpenButton.HorizontalAlignment = HorizontalAlignment.Right
                OpenButton.Tag = ChildDataGrid
                Result._grid.Children.Add(OpenButton)
                AddHandler OpenButton.Click, AddressOf Result.HandleChildTableOpenButtonClick
                RightX += 61
            End If

            ' Create the button to edit an existing record
            If ChildTable.CanEditRecords Then
                Dim EditButton As Button = ControlGenerator.GenerateButton("Edit", 55, 23, New Thickness(0, Location.Y, RightX, 0))
                EditButton.HorizontalAlignment = HorizontalAlignment.Right
                EditButton.Tag = ChildDataGrid
                Result._grid.Children.Add(EditButton)
                AddHandler EditButton.Click, AddressOf Result.HandleChildTableEditButtonClick
                RightX += 61
            End If

            ' Create the button to delete an existing record
            If ChildTable.CanDeleteRecords Then
                Dim DeleteButton As Button = ControlGenerator.GenerateButton("Delete", 55, 23, New Thickness(0, Location.Y, RightX, 0))
                DeleteButton.HorizontalAlignment = HorizontalAlignment.Right
                DeleteButton.Tag = ChildDataGrid
                Result._grid.Children.Add(DeleteButton)
                AddHandler DeleteButton.Click, AddressOf Result.HandleChildTableDeleteButtonClick
                RightX += 61
            End If

            Location.Y += 23 + 12

        Next

        ' Create the button to save
        Dim SaveButton As Button = ControlGenerator.GenerateButton("Save", 75, 23, New Thickness(0, 0, 99, 12))
        SaveButton.HorizontalAlignment = Windows.HorizontalAlignment.Right
        SaveButton.VerticalAlignment = Windows.VerticalAlignment.Bottom
        Result._grid.Children.Add(SaveButton)
        AddHandler SaveButton.Click, AddressOf Result.HandleSaveButtonClicked

        ' Create the button to cancel
        Dim CancelButton As Button = ControlGenerator.GenerateButton("Cancel", 75, 23, New Thickness(0, 0, 12, 12))
        CancelButton.HorizontalAlignment = Windows.HorizontalAlignment.Right
        CancelButton.VerticalAlignment = Windows.VerticalAlignment.Bottom
        Result._grid.Children.Add(CancelButton)
        AddHandler CancelButton.Click, AddressOf Result.HandleCancelButtonClicked

        ' Add menu items
        For Each MenuItem As MenuItemAttribute In table.MenuItems

            ' Add a menu item click handler
            Dim ClickHandler As RoutedEventHandler = Nothing
            Dim MethodInfo As MethodInfo = table.EntityType.GetMethod(MenuItem.ClickHandler)
            If MethodInfo IsNot Nothing Then
                ClickHandler = [Delegate].CreateDelegate(GetType(RoutedEventHandler), existingEntity, MethodInfo)
            End If

            ' Add the menu item itself
            If MenuItem.OnEditForm Then Result.AddMenuItem(MenuItem.Category, MenuItem.ItemText, ClickHandler)

        Next

        ' Populate the form if an existing entity was supplied
        If existingEntity IsNot Nothing Then Result.UpdateFormControls()

        ' Do we need to populate the field indicating the parent record?
        If parentEntity IsNot Nothing AndAlso parentEntity.Schema IsNot Nothing AndAlso existingEntity Is Nothing Then
            Dim ParentPrimaryKey As String = parentEntity.Schema.PrimaryKey
            Dim Value As Object = parentEntity.GetValue(ParentPrimaryKey)
            Result.SetControlValue(ParentPrimaryKey, Value)
        End If

        ' Return the window
        Result._window.Height = Location.Y + 80
        Return Result

    End Function

    Private Function CreateControl(ByRef location As Point, ByVal field As FieldSchema) As Control

        Dim Result As Control = Nothing

        ' Is this a 'typical' field?
        If field.AssignedPickList Is Nothing AndAlso field.AssignedLookup Is Nothing Then

            If field.FieldType Is GetType(DateTime) Then

                ' Create a date control
                Dim Control As New TimePicker
                Control.Margin = New Thickness(location.X, location.Y, 12, 0)
                Control.HorizontalAlignment = HorizontalAlignment.Left
                Control.SelectedDate = Nothing
                If field.DateDetails IsNot Nothing Then
                    Control.IsDateVisible = field.DateDetails.DateImportant
                    Control.IsTimeVisible = field.DateDetails.TimeImportant
                End If
                Result = Control
                location.Y += 30

            ElseIf field.FieldType Is GetType(Boolean) Then

                ' Create a checkbox control
                Dim Control As New CheckBox
                Control.Width = 120
                Control.Margin = New Thickness(location.X, location.Y, 0, 0)
                Control.HorizontalAlignment = HorizontalAlignment.Left
                Result = Control
                location.Y += 30

            Else

                ' Anything else defaults to a text box
                If field.IsPasswordField Then Result = New PasswordBox Else Result = New TextBox
                Result.Margin = New Thickness(location.X, location.Y, 12, 0)

                ' Shorten the width of the text box for certain controls
                If TypeIsNumericType(field.FieldType) Then
                    Result.Width = 80
                    Result.Margin = New Thickness(location.X, location.Y, 0, 0)
                End If

                ' Memo fields are larger
                If field.IsMemoField Then
                    Result.Height = 100
                    DirectCast(Result, TextBox).AcceptsReturn = True
                    DirectCast(Result, TextBox).AcceptsTab = True
                    DirectCast(Result, TextBox).HorizontalScrollBarVisibility = ScrollBarVisibility.Auto
                    DirectCast(Result, TextBox).VerticalScrollBarVisibility = ScrollBarVisibility.Auto
                    location.Y += 112
                Else
                    location.Y += 30
                End If

            End If

        ElseIf field.AssignedPickList IsNot Nothing Then

            ' The field is a pick list
            Dim Control As New ComboBox
            Dim PickList As BasePickList = field.AssignedPickList.CreateInstance
            For Each StoreValue As String In PickList.AllStoreValues
                Dim Item As New ComboBoxItem
                Item.Content = PickList.GetDisplayValue(StoreValue)
                Item.Tag = StoreValue
                Control.Items.Add(Item)
            Next
            Control.Margin = New Thickness(location.X, location.Y, 12, 0)
            Result = Control
            location.Y += 30

        Else

            ' The field is a lookup
            Dim Control As New LookupControl(_database, _table.Database.Tables(field.AssignedLookup.TableName))
            Control.Margin = New Thickness(location.X, location.Y, 12, 0)
            Result = Control
            location.Y += 30

        End If

        ' Ensure a control was assigned
        If Result Is Nothing Then Throw New Exception("Could not create a control for the field '" + field.Name + "'.")

        ' Set common properties for the control
        Result.VerticalAlignment = VerticalAlignment.Top
        Result.Tag = field

        ' Return the control
        Return Result

    End Function

    Private Sub UpdateFormControls()

        _existingEntity.CalculateComputedFields()

        ' Map each control to its field
        For Each Field As FieldSchema In _table.AllFields
            For Each Control As Control In Me._grid.Children

                ' Is this the correct mapping?
                If Control.Tag Is Field Then

                    ' Set the value of the field
                    Dim FieldValue As Object = Field.GetValue(_existingEntity)
                    SetControlValue(Control, FieldValue)

                End If

            Next
        Next

    End Sub

    Private Sub SetControlValue(ByVal fieldName As String, ByVal fieldValue As Object)

        ' First, find the associated control
        For Each Control As Control In Me._grid.Children
            Dim Field As FieldSchema = Control.Tag
            If Field IsNot Nothing AndAlso Field.Name = fieldName Then

                ' Next, set the value of the control
                SetControlValue(Control, fieldValue)

            End If
        Next

    End Sub

    Private Sub SetControlValue(ByVal control As Control, ByVal fieldValue As Object)

        ' Get the associated field
        Dim Field As FieldSchema = control.Tag

        If TypeOf control Is TextBox Then DirectCast(control, TextBox).Text = Convert.ToString(fieldValue)
        If TypeOf control Is CheckBox Then DirectCast(control, CheckBox).IsChecked = Convert.ToBoolean(fieldValue)
        If TypeOf control Is TimePicker Then DirectCast(control, TimePicker).SelectedDate = Convert.ToDateTime(fieldValue)

        ' Special case for pick lists
        If TypeOf control Is ComboBox Then
            Dim PickList As BasePickList = Field.AssignedPickList.CreateInstance()
            Dim Index As Integer = Array.IndexOf(PickList.AllStoreValues, Convert.ToString(fieldValue))
            DirectCast(control, ComboBox).SelectedIndex = Index
        End If

        ' Special case for lookups
        If TypeOf control Is LookupControl Then
            Dim LookupTable As TableSchema = Field.Table.Database.Tables(Field.AssignedLookup.TableName)
            Dim Record As BaseEntity = _database.Tables(Field.AssignedLookup.TableName).FindRecord(LookupTable.PrimaryKey, fieldValue)
            DirectCast(control, LookupControl).SelectedRecord = Record
        End If

    End Sub

    Private Function ComposeOrUpdateEntity() As BaseEntity

        ' Create the entity
        Dim Entity As BaseEntity = _existingEntity
        If Entity Is Nothing Then Entity = _table.CreateEntityType()

        ' Populate the entity based on the value of controls on the form
        For Each Control As Control In Me._grid.Children
            If TypeOf Control.Tag Is FieldSchema Then
                Dim Field As FieldSchema = Control.Tag

                If TypeOf Control Is TextBox Then Field.SetValue(Entity, DirectCast(Control, TextBox).Text)
                If TypeOf Control Is CheckBox Then Field.SetValue(Entity, DirectCast(Control, CheckBox).IsChecked)
                If TypeOf Control Is TimePicker Then Field.SetValue(Entity, DirectCast(Control, TimePicker).SelectedDate)

                ' Pick list
                If TypeOf Control Is ComboBox Then
                    Dim Item As ComboBoxItem = DirectCast(Control, ComboBox).SelectedItem
                    If Item IsNot Nothing Then Field.SetValue(Entity, Item.Tag)
                End If

                ' Lookup
                If TypeOf Control Is LookupControl Then
                    Dim LookupControl As LookupControl = Control
                    Dim SelectedRecord As BaseEntity = LookupControl.SelectedRecord
                    Dim Value As Object = Nothing
                    If SelectedRecord IsNot Nothing Then
                        Value = _table.Database.Tables(LookupControl.Table.Name).GetPrimaryKeyValue(SelectedRecord)
                    End If
                    Field.SetValue(Entity, Value)
                End If

            End If
        Next

        ' Return the entity
        Return Entity

    End Function

    Private Sub HandleSaveButtonClicked(ByVal sender As Object, ByVal e As RoutedEventArgs)

        ' Update the entity
        Dim Entity As BaseEntity = ComposeOrUpdateEntity()

        ' Validate the entity
        Dim Errors As String() = Nothing
        Dim Warnings As String() = Nothing
        Entity.Validate(Errors, Warnings)
        If Errors.Length > 0 Then

            ' Show the errors to the user
            Dim MessageText As String = "Please fix the following " + Errors.Length.ToString + " validation error(s):" + Environment.NewLine
            If Errors.Length = 1 Then MessageText = "Please fix the following validation error:" + Environment.NewLine
            For Each Message As String In Errors
                MessageText += Environment.NewLine + Message
            Next
            MsgBox(MessageText, MsgBoxStyle.Critical, "Cannot Save")

        Else

            ' Are there warnings?
            Dim Proceed As MsgBoxResult = MsgBoxResult.Yes
            If Warnings.Length > 0 Then

                ' Show the warnings to the user
                Dim MessageText As String = "Please note the following " + Warnings.Length.ToString + " validation warnings(s):" + Environment.NewLine
                If Warnings.Length = 1 Then MessageText = "Please note the following validation warning:" + Environment.NewLine
                For Each Message As String In Warnings
                    MessageText += Environment.NewLine + Message
                Next
                MessageText += Environment.NewLine + Environment.NewLine + "Do you wish to save anyway?"
                Proceed = MsgBox(MessageText, MsgBoxStyle.Exclamation + MsgBoxStyle.YesNo, "Warning")

            End If

            ' Save the entity
            If Proceed = MsgBoxResult.Yes Then
                If _existingEntity Is Nothing Then
                    _database.Tables(_table.Name).AppendRecord(Entity)
                Else
                    _database.Tables(_table.Name).UpdateRecord(Entity)
                End If

                ' Close the form
                _window.DialogResult = True

            End If

        End If

    End Sub

    Private Sub HandleCancelButtonClicked(ByVal sender As Object, ByVal e As RoutedEventArgs)
        _window.DialogResult = False
    End Sub

    Private Sub HandleChildTableInsertButtonClick(ByVal sender As Object, ByVal e As RoutedEventArgs)

        ' Get information about the related table
        Dim ChildGrid As DataGrid = DirectCast(sender, Button).Tag
        Dim ChildSchema As TableSchema = ChildGrid.Tag

        ' Generate the edit form
        Dim EditForm As EditWindow = EditWindow.Generate(_database, ChildSchema, parentEntity:=_existingEntity)

        ' Show the window
        If EditForm.ShowDialog = True Then
            DataGridManager.Update(ChildGrid, _database, ChildSchema, _table.PrimaryKey, _table.GetPrimaryKeyValue(_existingEntity))
        End If

    End Sub

    Private Sub HandleChildTableOpenButtonClick(ByVal sender As Object, ByVal e As RoutedEventArgs)

        ' Get variables
        Dim ChildGrid As DataGrid = DirectCast(sender, Button).Tag
        Dim ChildSchema As TableSchema = ChildGrid.Tag

        ' Ensure a record has been selected
        Dim SelectedRecord As BaseEntity = ChildGrid.SelectedItem
        If SelectedRecord Is Nothing Then
            MsgBox("Please select a " + ChildSchema.SingularCaption + " to view first.", MsgBoxStyle.Critical)
            Exit Sub
        End If

        ' Open the record
        Dim EditForm As EditWindow = EditWindow.Generate(_database, ChildSchema, SelectedRecord)
        EditForm.ShowDialog()

    End Sub

    Private Sub HandleChildTableEditButtonClick(ByVal sender As Object, ByVal e As RoutedEventArgs)

        ' Get variables
        Dim ChildGrid As DataGrid = DirectCast(sender, Button).Tag
        Dim ChildSchema As TableSchema = ChildGrid.Tag

        ' Ensure a record has been selected
        Dim SelectedRecord As BaseEntity = ChildGrid.SelectedItem
        If SelectedRecord Is Nothing Then
            MsgBox("Please select a " + ChildSchema.SingularCaption + " to edit first.", MsgBoxStyle.Critical)
            Exit Sub
        End If

        ' Edit the record
        Dim EditForm As EditWindow = EditWindow.Generate(_database, ChildSchema, SelectedRecord)
        If EditForm.ShowDialog = True Then DataGridManager.Update(ChildGrid, _database, ChildSchema, _table.PrimaryKey, _table.GetPrimaryKeyValue(_existingEntity))


    End Sub

    Private Sub HandleChildTableDeleteButtonClick(ByVal sender As Object, ByVal e As RoutedEventArgs)

        ' Get variables
        Dim ChildGrid As DataGrid = DirectCast(sender, Button).Tag
        Dim ChildSchema As TableSchema = ChildGrid.Tag

        ' Ensure a record has been selected
        Dim SelectedRecord As BaseEntity = ChildGrid.SelectedItem
        If SelectedRecord Is Nothing Then
            MsgBox("Please select a " + ChildSchema.SingularCaption + " to delete first.", MsgBoxStyle.Critical)
            Exit Sub
        End If

        ' Ask the user to confirm deleting the record
        If MsgBox("Are you sure you wish to delete the selected " + ChildSchema.SingularCaption + "?", MsgBoxStyle.Question + MsgBoxStyle.YesNo) = MsgBoxResult.Yes Then

            ' Delete the record
            _database.Tables(ChildSchema.Name).DeleteRecord(SelectedRecord)
            DataGridManager.Update(ChildGrid, _database, ChildSchema, _table.PrimaryKey, _table.GetPrimaryKeyValue(_existingEntity))

        End If

    End Sub

End Class
